/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wdullaer.materialdatetimepicker.date;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayoutManager;
import ohos.agp.components.ListContainer;
import ohos.agp.text.Font;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

import com.wdullaer.materialdatetimepicker.Utils;
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog.OnDateChangedListener;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

/**
 * This displays a list of months in a calendar format with selectable days.
 */
public abstract class DayPickerView extends ListContainer implements OnDateChangedListener {

    private static final String TAG = "MonthFragment";

    private Font font;

    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 1234567, TAG);

    protected Context mContext;

    // highlighted time
    protected MonthAdapter.CalendarDay mSelectedDay;
    protected MonthAdapter mAdapter;

    protected MonthAdapter.CalendarDay mTempDay;

    // which month should be displayed/highlighted [0-11]
    protected int mCurrentMonthDisplayed;

    private OnPageListener pageListener;
    private DatePickerController mController;

    public interface OnPageListener {
        /**
         * Called when the visible page of the DayPickerView has changed
         *
         * @param position the new position visible in the DayPickerView
         */
        void onPageChanged(int position);
    }

    @Override
    public Component getComponentAt(int index) {
        return mAdapter.getComponentAt(index);
    }

    public DayPickerView(Context context, AttrSet attrs) {
        super(context, attrs);
        DatePickerDialog.ScrollOrientation scrollOrientation = DatePickerDialog.ScrollOrientation.HORIZONTAL;
        init(context, scrollOrientation);
    }

    public DayPickerView(Context context, DatePickerController controller, Font font) {
        super(context);
        this.font = font;
        init(context, controller.getScrollOrientation());
        setController(controller);
    }

    protected void setController(DatePickerController controller) {
        mController = controller;
        mController.registerOnDateChangedListener(this);
        mSelectedDay = new MonthAdapter.CalendarDay(mController.getTimeZone());
        mTempDay = new MonthAdapter.CalendarDay(mController.getTimeZone());
        refreshAdapter();
    }

    public void init(Context context, DatePickerDialog.ScrollOrientation scrollOrientation) {
        int layoutOrientation = scrollOrientation == DatePickerDialog.ScrollOrientation.VERTICAL
            ? VERTICAL
            : HORIZONTAL;
        DirectionalLayoutManager linearLayoutManager = new DirectionalLayoutManager();
        linearLayoutManager.setOrientation(layoutOrientation);
        setLayoutManager(linearLayoutManager);
        setLayoutConfig(new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));
        enableScrollBar(AXIS_X, false);
        enableScrollBar(AXIS_Y, false);
//        setClipChildren(false);

        mContext = context;
        setUpRecyclerView(scrollOrientation);
    }

    /**
     * Sets all the required fields for the list view. Override this method to
     * set a different list view behavior.
     *
     * @param scrollOrientation 滚动方向
     */
    protected void setUpRecyclerView(DatePickerDialog.ScrollOrientation scrollOrientation) {
        enableScrollBar(AXIS_Y, false);
        setFadeEffectBoundaryWidth(0);
    }

    public void onChange() {
        refreshAdapter();
    }

    /**
     * Creates a new adapter if necessary and sets up its parameters. Override
     * this method to provide a custom adapter.
     */
    protected void refreshAdapter() {
        if (mAdapter == null) {
            mAdapter = createMonthAdapter(mController, font);
        } else {
            mAdapter.setSelectedDay(mSelectedDay);
            if (pageListener != null) pageListener.onPageChanged(getMostVisiblePosition());
        }
        // refresh the view with the new parameters
        setItemProvider(mAdapter);
    }

    public abstract MonthAdapter createMonthAdapter(DatePickerController controller, Font font);

    public void setOnPageListener(OnPageListener pageListener) {
        this.pageListener = pageListener;
    }

    @SuppressWarnings("unused")
    public OnPageListener getOnPageListener() {
        return pageListener;
    }

    /**
     * This moves to the specified time in the view. If the time is not already
     * in range it will move the list so that the first of the month containing
     * the time is at the top of the view. If the new time is already in view
     * the list will not be scrolled unless forceScroll is true. This time may
     * optionally be highlighted as selected as well.
     *
     * @param day The day to move to
     * @param animate Whether to scroll to the given time or just redraw at the
     * new location
     * @param setSelected Whether to set the given time as selected
     * @param forceScroll Whether to recenter even if the time is already
     * visible
     * @return Whether or not the view animated to the new location
     */
    public boolean goTo(MonthAdapter.CalendarDay day, boolean animate, boolean setSelected, boolean forceScroll) {

        HiLog.warn(LABEL, "### goTo(year:" + day.year + ", month:" + day.month + ", day:" + day.day);
        // Set the selected day
        if (setSelected) {
            mSelectedDay.set(day);
        }

        mTempDay.set(day);
        int minMonth = mController.getStartDate().get(Calendar.MONTH);
        final int position = (day.year - mController.getMinYear())
            * MonthAdapter.MONTHS_IN_YEAR + day.month - minMonth;

        HiLog.warn(LABEL, "DayPickerView goTo calc position:" + position);

        Component child;
        int i = getFirstVisibleItemPosition();
        int top = 0;
        // Find a child that's completely in the view
        do {
            child = getComponentAt(i++);
            if (child == null) {
                break;
            }
            top = child.getTop();
            Utils.log("child at " + (i - 1) + " has top " + top);
        } while (top < 0);

        // Compute the first and last position visible
        int selectedPosition = child != null ? mAdapter.getComponentPosition(child) : 0;

        HiLog.warn(LABEL, "DayPickerView goTo current selectedPosition:" + selectedPosition);

        if (setSelected) {
            mAdapter.setSelectedDay(mSelectedDay);
        }

        // Check if the selected day is now outside of our visible range
        // and if so scroll to the month that contains it
        if (position != selectedPosition || forceScroll) {
            setMonthDisplayed(mTempDay);
            if (animate) {
                HiLog.warn(LABEL, "DayPickerView goTo before scrollTo");
                scrollTo(position);
                if (pageListener != null) pageListener.onPageChanged(position);
                return true;
            } else {
                HiLog.warn(LABEL, "DayPickerView goTo before postSetSelection");
                postSetSelection(position);
            }
        } else if (setSelected) {
            setMonthDisplayed(mSelectedDay);
        }
        return false;
    }

    public void postSetSelection(final int position) {
        clearFocus();
        HiLog.warn(LABEL, "DayPickerView postSetSelection:" + position);
        new EventHandler(EventRunner.current()).postTask(() -> {
//            ((LinearLayoutManager) getLayoutManager()).scrollToPositionWithOffset(position, 0);

            HiLog.warn(LABEL, "DayPickerView postSetSelection scrollTo:" + position);
            scrollTo(position);

            // Set initial accessibility focus to selected day
//            restoreAccessibilityFocus(mSelectedDay);

            if (pageListener != null) pageListener.onPageChanged(position);
        });
    }

    /**
     * 集月显示
     * Sets the month displayed at the top of this view based on time. Override
     * to add custom events when the title is changed.
     *
     * @param date 日期
     */
    protected void setMonthDisplayed(MonthAdapter.CalendarDay date) {
        mCurrentMonthDisplayed = date.month;
    }

    /**
     * 得到最明显的位置
     * Gets the position of the view that is most prominently displayed within the list.
     *
     * @return int
     */
    public int getMostVisiblePosition() {
        HiLog.warn(LABEL, "### DayPickerView getMostVisiblePosition :" + mAdapter.getComponentPosition(getMostVisibleMonth()));
        return mAdapter.getComponentPosition(getMostVisibleMonth());
    }

    public MonthView getMostVisibleMonth() {
        boolean verticalScroll = mController.getScrollOrientation() == DatePickerDialog.ScrollOrientation.VERTICAL;
        final int maxSize = verticalScroll ? getHeight() : getWidth();
        int maxDisplayedSize = 0;
        int i = getFirstVisibleItemPosition();
        int size = 0;
        MonthView mostVisibleMonth = null;

        while (size < maxSize) {
            Component child = getComponentAt(i);
            if (child == null) {
                break;
            }
            size = verticalScroll ? child.getBottom() : child.getRight();
            int endPosition = verticalScroll ? child.getTop() : child.getLeft();
            int displayedSize = Math.min(size, maxSize) - Math.max(0, endPosition);
            if (displayedSize > maxDisplayedSize) {
                mostVisibleMonth = (MonthView) child;
                maxDisplayedSize = displayedSize;
            }
            i++;
        }

        HiLog.warn(LABEL, "### DayPickerView getMostVisibleMonth :" + mostVisibleMonth);
        return mostVisibleMonth;
    }

    public int getCount() {
        return mAdapter.getCount();
    }

    /**
     * This should only be called when the DayPickerView is visible, or when it has already been
     * requested to be visible
     */
    @Override
    public void onDateChanged() {
        goTo(mController.getSelectedDay(), false, true, true);
    }

    void accessibilityAnnouncePageChanged() {
        MonthView mv = getMostVisibleMonth();
        if (mv != null) {
            String monthYear = getMonthAndYearString(mv.mMonth, mv.mYear, mController.getLocale());
            Utils.tryAccessibilityAnnounce(this, monthYear);
        }
    }

    private static String getMonthAndYearString(int month, int year, Locale locale) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.MONTH, month);
        calendar.set(Calendar.YEAR, year);
        HiLog.warn(LABEL, "### DayPickerView getMonthAndYearString year:" + year + ", month:" + month);
        return new SimpleDateFormat("MMMM yyyy", locale).format(calendar.getTime());
    }
}
